
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <memory>
#include <optional>
#include <redis.h>
#include <string>
#include <sw/redis++/redis.h>

#include "problem.pb.h"
#include "Problem.hpp"



namespace lyt {
    class ProblemRedis{
    private:
        std::shared_ptr<sw::redis::Redis> _client;

        problem::ProblemRedis ToProto(const Problem& p) {
            problem::ProblemRedis proto;
            proto.set_id(p.problem_id);
            proto.set_title(p.title);
            proto.set_difficulty(p.difficulty);
            proto.set_acceptance_rate(p.acceptance_rate);     
            proto.set_description(p.description);
            proto.set_test_case_hint(p.case_hint);
            return proto;
        }

        Problem FromProto(const problem::ProblemRedis& proto) {
            return Problem{
                proto.id(),
                proto.title(),
                proto.difficulty(),
                proto.acceptance_rate(),
                proto.description(),
                proto.test_case_hint()
            };
        }
    public:
        using ptr = std::shared_ptr<ProblemRedis>;
        ProblemRedis(const std::shared_ptr<sw::redis::Redis> client)
                    :_client(client){}
        // ProblemRedis
        // uint32 id = 1;               // 题目唯一ID
        // string title = 2;            // 题目名称
        // uint32 difficulty = 3;       // 难度(easy/medium/hard)
        // float acceptance_rate = 4;   // 通过率
        // string description = 5;      // 题目描述(Markdown)
        // string test_case_hint        // 测试用例提示范围
        // ========================================Problem===============================
        // Problem对象写入缓存
        void set_problem_in_redis(const Problem& p){
            std::string key = "problem:" + std::to_string(p.problem_id);
            problem::ProblemRedis proto = ToProto(p);
            std::string serialized;
            proto.SerializeToString(&serialized);
            // 十分钟缓存
            _client->setex(key,std::chrono::seconds(600),serialized);
        }
        // 获取Problem表
        // std::optional<T> 是 C++17 引入的模板类，用来 表示一个值可能存在，也可能不存在。
        // 有值时：可以访问
        // 无值时：避免空指针、未定义行为
        std::optional<Problem> get_problem_in_redis(const uint64_t id){
            std::string key = "problem:" + std::to_string(id);
            auto res = _client.get()->get(key);
            if(!res) return std::nullopt;

            problem::ProblemRedis proto;
            if(!proto.ParseFromString(*res)) return std::nullopt;

            return FromProto(proto);
        }
        // 删除缓存
        void del_problem_in_redis(const uint64_t id){
            _client->del("problem:" + std::to_string(id));
        }
        // 获取token对应的用户id
        std::optional<std::string> GetUserId(const std::string &token) {
            try {
                auto val = _client->get(token);
                if (val)
                    return val;
            } catch (const sw::redis::Error &err) {
                errorlog << "GetUserId error!" << err.what();
            }
            return std::nullopt;
        }

        // ========================================ProblemSolved===============================
        // 添加到已解决集合
        void solved(const std::string& user_id, int problem_id) {
            _client->sadd(solved_key(user_id), std::to_string(problem_id));
        }

        // 添加到尝试中集合
        void mark_attempting(const std::string& user_id, int problem_id) {
            _client->sadd(attempting_key(user_id), std::to_string(problem_id));
        }

        // 判断是否已解决
        bool is_solved(const std::string& user_id, int problem_id) {
            return _client->sismember(solved_key(user_id), std::to_string(problem_id));
        }

        // 判断是否尝试中
        bool is_attempting(const std::string& user_id, int problem_id) {
            return _client->sismember(attempting_key(user_id), std::to_string(problem_id));
        }

        // 获取已解决题目id列表
        std::vector<uint32_t> get_solved_problems(const std::string& user_id) {
            std::vector<uint32_t> result;
            std::vector<std::string> member;
            _client->smembers(solved_key(user_id),std::back_inserter(member));
            for(const auto& e:member){
                result.push_back(std::stoi(e));
            }
            return result;
        }

        // 获取尝试中题目 ID 列表
        std::vector<uint32_t> get_attempting_problems(const std::string& user_id) {
            std::vector<uint32_t> result;
            std::vector<std::string> member;
            _client->smembers(attempting_key(user_id),std::back_inserter(member));
            for(const auto& e:member){
                result.push_back(std::stoi(e));
            }
            return result;
        }

        // 从已解决集合中移除题目
        void remove_solved(const std::string& user_id, int problem_id) {
            _client->srem(solved_key(user_id), std::to_string(problem_id));
        }

        // 从尝试中集合中移除题目
        void remove_attempting(const std::string& user_id, int problem_id) {
            _client->srem(attempting_key(user_id), std::to_string(problem_id));
        }

        // ========================================Problem acceptance_rate===============================
        void add_submission_sum(const uint32_t problem_id){
            std::string key = "problem:stats:" + std::to_string(problem_id);
            _client->hincrby(key, "total_submissions", 1);
        }
        void add_accept_sum(const uint32_t problem_id){
            std::string key = "problem:stats:" + std::to_string(problem_id);
            _client->hincrby(key, "total_accepted", 1);
        }
        // 设置题目通过率
        void set_acceptance_rate(const uint32_t problem_id, float acceptance_rate) {
            std::string key = "problem:acceptance_rate:" + std::to_string(problem_id);
            _client->set(key,std::to_string(acceptance_rate),std::chrono::seconds(120));
        }
        // 获取题目通过率(小于0表示通过率过期，需要重新计算)
        float get_acceptance_rate(const uint32_t problem_id) {
            std::string key = "problem:acceptance_rate:" + std::to_string(problem_id);
            auto rate_str = _client->get(key);
            if (!rate_str) return -1.0f;
            return std::stof(*rate_str);
        }

        float cal_acceptance_rate(const uint32_t problem_id) {
            std::string key = "problem:stats:" + std::to_string(problem_id);

            try {
                // 从 Redis 获取 total_accepted 和 total_submissions
                auto accepted = _client->hget(key, "total_accepted");
                auto submissions = _client->hget(key, "total_submissions");

                // 如果两个字段都存在
                if (accepted && submissions) {
                    int accepted_count = std::stoi(*accepted);
                    int submission_count = std::stoi(*submissions);

                    float rate = 0.0f;
                    if (submission_count > 0) {
                        // 把accepted_count提升为float类型从而sub也提升（进行浮点数除法）
                        rate = static_cast<float>(accepted_count) / submission_count;
                    }

                    // 存入通过率
                    set_acceptance_rate(problem_id, rate);
                    return rate;
                } else {
                    // 字段缺失，视情况设为 0 或报错
                    set_acceptance_rate(problem_id, 0.0f);
                    return -1.0f;
                }
            } catch (const sw::redis::Error &err) {
                std::cerr << "Redis error in cal_acceptance_rate: " << err.what() << std::endl;
                return -1.0f;
            }
        }

        
    private:
        std::string solved_key(const std::string& user_id) {
            return "problem_solved:" + user_id;
        }
        std::string attempting_key(const std::string& user_id) {
            return "problem_attempting:" + user_id;
        }
    };
}